package com.lyon.demo.storage.common.spi;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.lyon.demo.storage.common.spi.annotation.Signleton;
import com.lyon.demo.storage.common.spi.annotation.SpiActivate;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author LeeYan9
 * @since 2022-04-15
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class DefaultSpiLoader implements SpiLoader {

    private DefaultSpiLoader() {
    }

    public static final SpiLoader INSTANCE = new DefaultSpiLoader();

    private final Map<Class, Object> CACHE_CONTAINER = new ConcurrentHashMap<>();

    private final Predicate<ServiceLoader.Provider> CACHE_PREDICATE = sProvider -> {
        Signleton annotation = (Signleton) sProvider.type().getAnnotation(Signleton.class);
        return Objects.isNull(annotation) || !CACHE_CONTAINER.containsKey(sProvider.type());
    };

    private final BiPredicate<ServiceLoader.Provider, String> STRATEGY_PREDICATE = (sProvider, strategy) -> {
        if (Objects.isNull(strategy)) {
            return false;
        }
        SpiActivate annotation = (SpiActivate) sProvider.type().getAnnotation(SpiActivate.class);
        return Objects.nonNull(annotation) && CharSequenceUtil.equals(strategy, annotation.value());
    };

    private <S> S get(ServiceLoader.Provider<S> sProvider) {
        S instance = sProvider.get();
        Signleton annotation = sProvider.type().getAnnotation(Signleton.class);
        if (Objects.nonNull(annotation)) {
            CACHE_CONTAINER.put(sProvider.type(), instance);
        }
        return instance;
    }

    @Override
    public <S> List<S> loader(Class<S> clazz) {
        return ServiceLoader
                .load(clazz)
                .stream()
//                .filter(CACHE_PREDICATE)
                .map(this::get)
                .collect(Collectors.toList());
    }

    @Override
    public <S> S loaderSingle(Class<S> clazz) {
        List<S> loaders = loader(clazz);
        return CollUtil.isEmpty(loaders) ? null : loaders.get(0);
    }

    @Override
    public <S> S loader(Class<S> clazz, String strategy) {
        return ServiceLoader
                .load(clazz)
                .stream()
//                .filter(CACHE_PREDICATE)
                .filter(sProvider -> STRATEGY_PREDICATE.test(sProvider, strategy))
                .map(this::get)
                .findFirst()
                .orElse(null);
    }
}
